/*
 * Solar.c
 *
 * Program to demonstrate how to use a local
 * coordinate method to position parts of a 
 * model in relation to other model parts.
 *
 * Draws a simple solar system, with a sun, planet and moon.
 * Based on sample code from the OpenGL programming guide
 *		by Woo, Neider, Davis.  Addison-Wesley.
 *
 * Author: Samuel R. Buss
 *
 * Software accompanying the book
 *		3D Computer Graphics: A Mathematical Introduction with OpenGL,
 *		by S. Buss, Cambridge University Press, 2003.
 *
 * Software is "as-is" and carries no warranty.  It may be used without
 *   restriction, but if you modify it, please change the filenames to
 *   prevent confusion between different versions.
 * Bug reports: Sam Buss, sbuss@ucsd.edu.
 * Web page: http://math.ucsd.edu/~sbuss/MathCG
 *
 * USAGE:
 *    Press "r" key to toggle (off and on) running the animation
 *    Press "s" key to single-step animation
 *    The up arrow key and down array key control the
 *			time step used in the animation rate.  Each key
 *			press multiplies or divides the times by a factor 
 *			of two (2).
 *	  Press ESCAPE to exit.
 *
 */


#include "Solar.h"   
#include <stdlib.h> 
#include <unistd.h>     // Header file for sleeping.
#include <stdio.h>
#include <math.h>
#include <GL/glut.h>	// OpenGL Graphics Utility Library

GLUquadricObj *sun, *earth, *moon;
//定义太阳材质
GLfloat mat_sun_ambient[] = {1.0f, 1.0f, 1.0f, 1.0f};
GLfloat mat_sun_diffuse[] = {1.0f, 1.0f, 1.0f, 1.0f};
GLfloat mat_sun_specular[] = {1.0f, 1.0f, 1.0f, 1.0f};
GLfloat mat_sun_emision[] = {1.0f, 1.0f, 1.0f, 1.0f};
GLfloat mat_sun_shininess[] = {0.0f};
//
//定义地球材质
GLfloat mat_earth_ambient[] = {0.1f, 0.0f, 0.1f, 1.0f};
GLfloat mat_earth_diffuse[] = {1.0f, 1.0f, 1.0f, 1.0f};
GLfloat mat_earth_specular[] = {0.0f, 0.0f, 1.0f, 1.0f};
GLfloat mat_earth_emision[] = {0.0f, 0.0f, 0.1f, 1.0f};
GLfloat mat_earth_shininess[] = {64.0f};
//定义月球材质
GLfloat mat_moon_ambient[] = {0.1f, 0.1f, 0.0f, 1.0f};
GLfloat mat_moon_diffuse[] = {0.5f, 0.5f, 0.5f, 1.0f};
GLfloat mat_moon_specular[] = {0.0f, 0.0f, 0.0f, 1.0f};
GLfloat mat_moon_emision[] = {0.1f, 0.1f, 0.0f, 1.0f};
GLfloat mat_moon_shininess[] = {0.0f};
//定义太阳光材质
GLfloat light_ambient[] = {0.2f, 0.2f, 0.2f, 1.0f};
GLfloat light_diffuse[] = {1.0f, 1.0f, 1.0f, 1.0f};
GLfloat light_specular[] = {1.0f, 1.0f, 1.0f, 1.0f};
GLfloat light_position[] = {0.0f, 0.0f, 0.0f, 1.0f};


//全局环境光参数
GLfloat lmodel_ambient[] = {0.4f, 0.4f, 0.4f, 1.0f};

static GLenum spinMode = GL_TRUE;
static GLenum singleStep = GL_FALSE;

// These three variables control the animation's state and speed.
static float HourOfDay = 0.0;
static float DayOfYear = 0.0;
static float AnimateIncrement = 24.0;  // Time step for animation (hours)


GLchar texture_path[][20] = 
{
	{"res/Sun.bmp"},
	{"res/Earth.bmp"},
	{"res/moon-copy.bmp"}
};
GLuint texture[3];

struct Image {
	unsigned long sizeX;
	unsigned long sizeY;
	char* data;
};
typedef struct Image Image;

int ImageLoad(char* filename, Image* image)
{
	FILE* file;
	unsigned long size;
	unsigned long i;
	unsigned short int planes;
	unsigned short int bpp;
	char temp;

	if ((file = fopen(filename, "r")) == NULL)
	{
		printf("File Not Found: %s\n", filename);
		return 0;
	}

	fseek(file, 18, SEEK_CUR);

	if((i = fread(&image->sizeX, 4, 1, file)) != 1)
	{
		printf("Error reading width from %s.\n", filename);
		return 0;
	}
	printf("Width of %s: %lu\n", filename, image->sizeX);

	if((i = fread(&image->sizeY, 4, 1, file)) != 1)
	{
		printf("Error reading height from %s.\n", filename);
		return 0;
	}
	printf("Height of %s: %lu\n", filename, image->sizeY);

	size = image->sizeX * image->sizeY * 3;

	if ((i = fread(&planes, 2, 1, file)) != 1)
	{
		printf("Planes from %s is not 1: %u\n", filename, planes);
		return 0;
	}

	if ((i = fread(&bpp, 2, 1, file)) != 1)
	{
		printf("Error reading bpp from %s.\n", filename);
		return 0;
	}

	if(bpp != 24)
	{
		printf("Bpp from %s is not 24: %u\n", filename, bpp);
		return 0;
	}

	fseek(file, 24, SEEK_CUR);

	image->data = (char*)malloc(size);
	if(image->data == NULL)
	{
		printf("Error allocation memory for color-corrected image data");
		return 0;
	}

	if((i = fread(image->data, size, 1, file)) != 1)
	{
		printf("Error readint image data from %s.\n", filename);
		return 0;
	}

	for (i = 0; i < size; i += 3)
	{
		temp = image->data[i];
		image->data[i] = image->data[i+2];
		image->data[i+2] = temp;
	}

	return 1;
}


void LoadGLTextures()
{
	Image* image1;

	int i = 0;
	for (i = 0; i < 3; ++i)
	{
		image1 = (Image*) malloc(sizeof(Image));
		if(image1 == NULL)
		{
			printf("Error allocating space for image.");
			exit(0);
		}


		if (!ImageLoad(texture_path[i], image1))
		{
			exit(1);
		}

		glGenTextures(1, &texture[i]);

		glBindTexture(GL_TEXTURE_2D, texture[i]);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
		glTexImage2D(GL_TEXTURE_2D, 0, 3, image1->sizeX, image1->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, image1->data);


		if (image1 != NULL)
		{
			if(image1->data)
			{
				free(image1->data);
			}
			free(image1);
		}
	}
}

// glutKeyboardFunc is called below to set this function to handle
//		all normal key presses.  
static void KeyPressFunc( unsigned char Key, int x, int y )
{
	switch ( Key ) {
		case 'R':
		case 'r':
			Key_r();
			break;
		case 's':
		case 'S':
			Key_s();
			break;
		case 27:	// Escape key
			exit(1);
	}
}

// glutSpecialFunc is called below to set this function to handle
//		all special key presses.  See glut.h for the names of
//		special keys.
static void SpecialKeyFunc( int Key, int x, int y )
{
	switch ( Key ) {
		case GLUT_KEY_UP:		
			Key_up();
			break;
		case GLUT_KEY_DOWN:
			Key_down();
			break;
	}
}


static void Key_r(void)
{
	if ( singleStep ) {			// If ending single step mode
		singleStep = GL_FALSE;
		spinMode = GL_TRUE;		// Restart animation
	}
	else {
		spinMode = !spinMode;	// Toggle animation on and off.
	}
}

static void Key_s(void)
{
	singleStep = GL_TRUE;
	spinMode = GL_TRUE;
}

static void Key_up(void)
{
	AnimateIncrement *= 2.0;			// Double the animation time step
}

static void Key_down(void)
{
	AnimateIncrement /= 2.0;			// Halve the animation time step

}

static void draw_axis()
{
	glBegin(GL_LINES);
	glColor3f(1.0f, 0.0f, 0.0f);
	glVertex3f(0.0f, 0.0f, 0.0f);
	glVertex3f(2.0f, 0.0f, 0.0f);

	glColor3f(0.0f, 1.0f, 0.0f);
	glVertex3f(0.0f, 0.0f, 0.0f);
	glVertex3f(0.0f, 2.0f, 0.0f);

	glColor3f(0.0f, 0.0f, 1.0f);
	glVertex3f(0.0f, 0.0f, 0.0f);
	glVertex3f(0.0f, 0.0f, 2.0f);

	glEnd();
}

/*
 * Animate() handles the animation and the redrawing of the
 *		graphics window contents.
 */
static void Animate(void)
{
	// Clear the current matrix (Modelview)
	glLoadIdentity();
	// Clear the redering window
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glColor3f(1.0f, 0.0f, 0.0f);

	if (spinMode) {
		// Update the animation state
		HourOfDay += AnimateIncrement;
		DayOfYear += AnimateIncrement/24.0;

		HourOfDay = HourOfDay - ((int)(HourOfDay/24))*24;
		DayOfYear = DayOfYear - ((int)(DayOfYear/365))*365;
	}


	static cinta = 0.0f;
	gluLookAt(5.0 * sin(cinta * M_PI / 180.0f), 
			5.0f, 
			5.0f * cos(cinta * M_PI / 180.0f), 
			0.0f, 0.0f, 0.0f, 
			0.0f, 1.0f, 0.0f);
	cinta -= 1.0f;
	glDisable(GL_TEXTURE_2D);
	glEnable(GL_COLOR_MATERIAL);        //启动颜色材质
	draw_axis();

	glEnable(GL_TEXTURE_2D);
	glDisable(GL_COLOR_MATERIAL);        //启动颜色材质
	glPushMatrix();


	// Back off eight units to be able to view from the origin.
	//glTranslatef ( 0.0, 0.0, -8.0 );
	glLightfv(GL_LIGHT0, GL_POSITION, light_position);
	glLightfv(GL_LIGHT0, GL_AMBIENT, light_ambient);
	glLightfv(GL_LIGHT0, GL_DIFFUSE, light_diffuse);
	glLightfv(GL_LIGHT0, GL_SPECULAR, light_specular);
	glTranslatef( light_position[0], light_position[1], light_position[2]);
	// Rotate the plane of the elliptic
	// Draw the sun	-- as a yellow, wireframe sphere
	glPushMatrix();
	glBindTexture(GL_TEXTURE_2D, texture[0]);
	glMaterialfv(GL_FRONT, GL_AMBIENT, mat_sun_ambient);
	glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_sun_diffuse);
	glMaterialfv(GL_FRONT, GL_SPECULAR, mat_sun_specular);
	glMaterialfv(GL_FRONT, GL_SHININESS, mat_sun_shininess);
	glMaterialfv(GL_FRONT, GL_EMISSION, mat_sun_emision);
	sun = gluNewQuadric();
	gluQuadricDrawStyle(sun, GLU_FILL);
	gluQuadricNormals(sun, GLU_SMOOTH);
	gluQuadricTexture(sun, GL_TRUE);
	gluSphere(sun, 1.0f, 150.0f, 150.0f);
	glPopMatrix();


	// Draw the Earth
	// First position it around the sun
	//		Use DayOfYear to determine its position
	glRotatef( 360.0*DayOfYear/365.0, 0.0, 1.0, 0.0 );
	glTranslatef( 4.0, 0.0, 0.0 );
	glPushMatrix();						// Save matrix state
	// Second, rotate the earth on its axis.
	//		Use HourOfDay to determine its rotation.
	glRotatef( 360.0*HourOfDay/24.0, 0.0, 1.0, 0.0 );
	// Third, draw the earth as a wireframe sphere.
	glColor3f( 0.2, 0.2, 1.0 );
	glBindTexture(GL_TEXTURE_2D, texture[1]);

	glMaterialfv(GL_FRONT, GL_AMBIENT, mat_earth_ambient);
	glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_earth_diffuse);
	glMaterialfv(GL_FRONT, GL_SPECULAR, mat_earth_specular);
	glMaterialfv(GL_FRONT, GL_SHININESS, mat_earth_shininess);
	glMaterialfv(GL_FRONT, GL_EMISSION, mat_earth_emision);

	earth = gluNewQuadric();
	gluQuadricDrawStyle(earth, GLU_FILL);
	gluQuadricNormals(earth, GLU_SMOOTH);
	gluQuadricTexture(earth, GL_TRUE);
	gluSphere(earth, 0.4f, 100.0f, 100.0f);
	//glutSolidSphere( 0.4, 100, 100);
	glPopMatrix();						// Restore matrix state

	// Draw the moon.
	//	Use DayOfYear to control its rotation around the earth
	glRotatef( 360.0*12.0*DayOfYear/365.0, 0.0, 1.0, 0.0 );
	glTranslatef( 0.7, 0.0, 0.0 );
	glColor3f( 0.3, 0.7, 0.3 );
	glBindTexture(GL_TEXTURE_2D, texture[2]);
	glMaterialfv(GL_FRONT, GL_AMBIENT, mat_moon_ambient);
	glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_moon_diffuse);
	glMaterialfv(GL_FRONT, GL_SPECULAR, mat_moon_specular);
	glMaterialfv(GL_FRONT, GL_SHININESS, mat_moon_shininess);
	glMaterialfv(GL_FRONT, GL_EMISSION, mat_moon_emision);

	moon = gluNewQuadric();
	gluQuadricDrawStyle(moon, GLU_FILL);
	gluQuadricNormals(moon, GLU_SMOOTH);
	gluQuadricTexture(moon, GL_FALSE);
	gluSphere(moon, 0.1f, 100.0f, 100.0f);

	glPopMatrix();

	// Flush the pipeline, and swap the buffers
	glFlush();
	glutSwapBuffers();

	if ( singleStep ) {
		spinMode = GL_FALSE;
	}

	glutPostRedisplay();		// Request a re-draw for animation purposes

}

// Initialize OpenGL's rendering modes
void OpenGLInit(void)
{
	LoadGLTextures();
	glEnable(GL_TEXTURE_2D);
	glShadeModel( GL_SMOOTH );
	glClearColor( 0.0, 0.0, 0.0, 0.0 );
	glClearDepth( 1.0 );
	glEnable( GL_DEPTH_TEST );

	glEnable(GL_LIGHT0);
	glEnable(GL_LIGHTING);

	glLightModelfv(GL_LIGHT_MODEL_AMBIENT, lmodel_ambient);
}

// ResizeWindow is called when the window is resized
static void ResizeWindow(int w, int h)
{
	float aspectRatio;
	h = (h == 0) ? 1 : h;
	w = (w == 0) ? 1 : w;
	glViewport( 0, 0, w, h );	// View port uses whole window
	aspectRatio = (float)w/(float)h;

	// Set up the projection view matrix (not very well!)
	glMatrixMode( GL_PROJECTION );
	glLoadIdentity();
	gluPerspective( 45.0, aspectRatio, 0.01f, 100.0f );

	// Select the Modelview matrix
	glMatrixMode( GL_MODELVIEW );
	glLoadIdentity();
	gluLookAt(5.0f, 5.0f, 5.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f);
}


// Main routine
// Set up OpenGL, hook up callbacks, and start the main loop
int main( int argc, char** argv )
{
	// Need to double buffer for animation
	glutInit(&argc,argv);
	glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH );

	// Create and position the graphics window
	glutInitWindowPosition( 0, 0 );
	glutInitWindowSize( 600, 480 );
	glutCreateWindow( "Solar System Demo" );

	// Initialize OpenGL.
	OpenGLInit();

	// Set up callback functions for key presses
	glutKeyboardFunc( KeyPressFunc );
	glutSpecialFunc( SpecialKeyFunc );

	// Set up the callback function for resizing windows
	glutReshapeFunc( ResizeWindow );

	// Callback for graphics image redrawing
	glutDisplayFunc( Animate );

	/* Go fullscreen.  This is as soon as possible. */
	//glutFullScreen();

	// Start the main loop.  glutMainLoop never returns.
	glutMainLoop(  );

	return(0);			// Compiler requires this to be here. (Never reached)
}
